package com.ctrlspace.core.dao;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.channels.FileChannel;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.ctrlspace.config.ConfigurationManager;
import com.ctrlspace.core.exceptions.FileSystemException;

public class FileSystemDao {

	public File createFile(String fileName) throws FileSystemException {
		return createFile(fileName, "", null);
	}

	public File createFile(String fileName, String contents) throws FileSystemException {
		return createFile(fileName, contents, null);
	}

	public File createFile(String fileName, File template) throws FileSystemException {
		// Read file template before passing to actual process function
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new FileReader(template));

			StringBuilder contents = new StringBuilder();
			String line = "";
			try {
				while ((line = reader.readLine()) != null) {
					contents.append(line);
				}
			} catch (IOException e) {
				throw new FileSystemException(e);
			}

			return createFile(fileName, contents.toString(), null);
		} catch (FileNotFoundException e) {
			throw new FileSystemException(e);
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) {
					throw new FileSystemException(e);
				}
			}
		}
	}

	public File createFile(String fileName, File template, Map<String, String> replace) throws FileSystemException {
		// Copy template file and open
		File newFile = new File(fileName);
		copyFile(template, newFile);

		String line = "";
		StringBuilder templateBuilder = new StringBuilder();

		BufferedReader br = null;
		try {
			br = new BufferedReader(new FileReader(newFile));
			while ((line = br.readLine()) != null) {
				templateBuilder.append(line + "\n");
			}
		} catch (FileNotFoundException e) {
			throw new FileSystemException(e);
		} catch (IOException e) {
			throw new FileSystemException(e);
		} finally {
			if (br != null) {
				try {
					br.close();
				} catch (IOException e) {
					throw new FileSystemException(e);
				}
			}
		}

		return createFile(fileName, templateBuilder.toString(), replace);
	}

	public File createFile(String fileName, String template, Map<String, String> replace) throws FileSystemException {
		File newFile = new File(fileName);

		// Nothing to write to file, skip writing template and adding
		// replacements
		if (template.equals("")) {
			try {
				newFile.createNewFile();
			} catch (IOException e) {
				throw new FileSystemException(e);
			}
			return newFile;
		}

		if (replace != null) {
			// Do replacements before writing
			Set<String> keys = replace.keySet();
			Iterator<String> keyIterator = keys.iterator();

			while (keyIterator.hasNext()) {
				String key = keyIterator.next();
				String replacement = replace.get(key);

				// Replace all occurrences of key in template
				template = template.replace(key, replacement);
			}
		}

		// Write to file
		BufferedWriter bw = null;
		try {
			bw = new BufferedWriter(new FileWriter(newFile));
			bw.write(template);
		} catch (IOException e) {
			throw new FileSystemException(e);
		} finally {
			if (bw != null) {
				try {
					bw.close();
				} catch (IOException e) {
					throw new FileSystemException(e);
				}
			}
		}

		return newFile;
	}

	public File createFolder(String folderName) throws FileSystemException {
		File folder = new File(folderName);

		boolean createSuccess = folder.mkdirs();
		if (createSuccess == false) {
			throw new FileSystemException("Failed to create " + folderName + ".");
		}

		return folder;
	}

	public File rename(String oldPath, String newPath) throws FileSystemException {
		File oldFile = new File(oldPath);
		File newFile = new File(newPath);

		return rename(oldFile, newFile);
	}

	public File rename(File oldFile, File newFile) throws FileSystemException {
		boolean renameSuccess = oldFile.renameTo(newFile);
		if (renameSuccess == false) {
			throw new FileSystemException("Failed to rename " + oldFile.getPath() + " to " + newFile.getPath() + ".");
		}

		return newFile;
	}

	public void delete(String path) throws FileSystemException {
		File file = new File(path);
		delete(file);
	}

	public void delete(File file) throws FileSystemException {
		boolean deleteSuccess = file.delete();
		if (deleteSuccess == false) {
			throw new FileSystemException("Failed to delete " + file.getPath());
		}
	}

	public File copyFile(String sourcePath, String destPath) throws FileSystemException {
		return copyFile(sourcePath, destPath, false);
	}

	public File copyFile(String sourcePath, String destPath, boolean overwrite) throws FileSystemException {
		File sourceFile = new File(sourcePath);
		File destFile = new File(destPath);

		return copyFile(sourceFile, destFile, overwrite);
	}

	public File copyFile(File sourceFile, File destFile) throws FileSystemException {
		return copyFile(sourceFile, destFile, false);
	}

	public File copyFile(File sourceFile, File destFile, boolean overwrite) throws FileSystemException {
		boolean fileExists = destFile.exists();

		// File exists and overwrite is false
		if (fileExists == true && overwrite == false) {
			throw new FileSystemException("Failed to copy file " + sourceFile.getPath() + ". File already exists.");
		}

		// Create destination file
		try {
			destFile.createNewFile();
		} catch (IOException e) {
			throw new FileSystemException(e);
		}

		// Copy file using Java NIO
		FileChannel source = null;
		FileChannel destination = null;
		try {
			source = new FileInputStream(sourceFile).getChannel();
			destination = new FileOutputStream(destFile).getChannel();
			destination.transferFrom(source, 0, source.size());
		} catch (FileNotFoundException e) {
			throw new FileSystemException(e);
		} catch (IOException e) {
			throw new FileSystemException(e);
		} finally {
			// Close streams
			if (source != null) {
				try {
					source.close();
				} catch (IOException e) {
					throw new FileSystemException(e);
				}
			}
			if (destination != null) {
				try {
					destination.close();
				} catch (IOException e) {
					throw new FileSystemException(e);
				}
			}
		}

		return destFile;
	}
	
	public File copyDirectory(String sourcePath, String destinationPath) throws FileSystemException {
		return copyDirectory(sourcePath, destinationPath, true, false);
	}
	
	public File copyDirectory(String sourcePath, String destinationPath, boolean copyStructure, boolean includeSubdirectories) throws FileSystemException {
		File sourceFile = new File(sourcePath);
		File destFile = new File(destinationPath);
		return copyDirectory(sourceFile, destFile, copyStructure, includeSubdirectories);
	}
	
	public File copyDirectory(File sourcePath, File destinationPath, boolean copyStructure, boolean includeSubDirectories) throws FileSystemException {
		if (!sourcePath.isDirectory()) {
			throw new FileSystemException("Source directory is not a folder. Consider using copyFile() instead.");
		}
		
		if (destinationPath.exists() && !destinationPath.isDirectory()) {
			throw new FileSystemException("Destination directory is not a folder. Consider using copyFile() instead.");
		}
		
		if (!destinationPath.exists()) {
			destinationPath = createFolder(destinationPath.getAbsolutePath());
		}
		
		File[] subfiles = sourcePath.listFiles();
		for (File f : subfiles) {
			if (f.isDirectory() && includeSubDirectories) {
				String newDestinationPath = "";
				if (copyStructure) {
					newDestinationPath = destinationPath.getAbsolutePath() + File.separator + f.getName();
				} else {
					newDestinationPath = destinationPath.getAbsolutePath();
				}
				copyDirectory(f.getAbsolutePath(), newDestinationPath, copyStructure, includeSubDirectories);
			} else if (f.isFile()) {
				copyFile(f.getAbsolutePath(), destinationPath.getAbsolutePath() + File.separator + f.getName(), true);
			}
		}
		
		return sourcePath;
	}

	public String openFile(String filePath) throws FileSystemException {
		return openFile(new File(filePath));
	}

	public String openFile(File file) throws FileSystemException {
		StringBuilder contents = new StringBuilder();

		BufferedReader input = null;
		try {
			input = new BufferedReader(new FileReader(file));
			String line = null;
			while ((line = input.readLine()) != null) {
				contents.append(line);
				contents.append(System.getProperty("line.separator"));
			}
		} catch (IOException ex) {
			throw new FileSystemException(ex);
		} finally {
			if (input != null) {
				try {
					input.close();
				} catch (IOException ex) {
					throw new FileSystemException(ex);
				}
			}
		}

		return contents.toString();
	}

	public void saveFile(File file, String contents) throws FileSystemException {
		if (file == null) {
			throw new FileSystemException("File should not be null.");
		}
		if (!file.exists()) {
			throw new FileSystemException("File does not exist: " + file);
		}
		if (!file.isFile()) {
			throw new FileSystemException("Should not be a directory: " + file);
		}
		if (!file.canWrite()) {
			throw new FileSystemException("File cannot be written: " + file);
		}

		Writer output = null;
		try {
			output = new BufferedWriter(new FileWriter(file));
			output.write(contents);
		} catch (IOException ex) {
			throw new FileSystemException(ex);
		} finally {
			if (output != null) {
				try {
					output.close();
				} catch (IOException ex) {
					throw new FileSystemException(ex);
				}
			}
		}
	}

}
